package org.thunderdog.challegram.video.old;

import android.annotation.TargetApi;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Build;

import com.coremedia.iso.boxes.AbstractMediaHeaderBox;
import com.coremedia.iso.boxes.SampleDescriptionBox;
import com.coremedia.iso.boxes.SoundMediaHeaderBox;
import com.coremedia.iso.boxes.VideoMediaHeaderBox;
import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry;
import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox;
import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.AudioSpecificConfig;
import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.DecoderConfigDescriptor;
import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.ESDescriptor;
import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.SLConfigDescriptor;
import com.mp4parser.iso14496.part15.AvcConfigurationBox;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class Track {

  private static class SamplePresentationTime {

    private int index;
    private long presentationTime;
    private long dt;

    public SamplePresentationTime(int idx, long time) {
      index = idx;
      presentationTime = time;
    }
  }

  private long trackId;
  private ArrayList<Sample> samples = new ArrayList<>();
  private long duration = 0;
  private int[] sampleCompositions;
  private String handler;
  private AbstractMediaHeaderBox headerBox;
  private SampleDescriptionBox sampleDescriptionBox;
  private LinkedList<Integer> syncSamples = null;
  private int timeScale;
  private Date creationTime = new Date();
  private int height;
  private int width;
  private float volume = 0;
  private long[] sampleDurations;
  private ArrayList<SamplePresentationTime> samplePresentationTimes = new ArrayList<>();
  private boolean isAudio;
  private static Map<Integer, Integer> samplingFrequencyIndexMap = new HashMap<>();
  private boolean first = true;

  static {
    samplingFrequencyIndexMap.put(96000, 0x0);
    samplingFrequencyIndexMap.put(88200, 0x1);
    samplingFrequencyIndexMap.put(64000, 0x2);
    samplingFrequencyIndexMap.put(48000, 0x3);
    samplingFrequencyIndexMap.put(44100, 0x4);
    samplingFrequencyIndexMap.put(32000, 0x5);
    samplingFrequencyIndexMap.put(24000, 0x6);
    samplingFrequencyIndexMap.put(22050, 0x7);
    samplingFrequencyIndexMap.put(16000, 0x8);
    samplingFrequencyIndexMap.put(12000, 0x9);
    samplingFrequencyIndexMap.put(11025, 0xa);
    samplingFrequencyIndexMap.put(8000, 0xb);
  }

  public Track(int id, MediaFormat format, boolean audio) {
    trackId = id;
    isAudio = audio;
    if (!isAudio) {
      width = format.getInteger(MediaFormat.KEY_WIDTH);
      height = format.getInteger(MediaFormat.KEY_HEIGHT);
      timeScale = 90000;
      syncSamples = new LinkedList<>();
      handler = "vide";
      headerBox = new VideoMediaHeaderBox();
      sampleDescriptionBox = new SampleDescriptionBox();
      String mime = format.getString(MediaFormat.KEY_MIME);
      if (mime.equals("video/avc")) {
        VisualSampleEntry visualSampleEntry = new VisualSampleEntry("avc1");
        visualSampleEntry.setDataReferenceIndex(1);
        visualSampleEntry.setDepth(24);
        visualSampleEntry.setFrameCount(1);
        visualSampleEntry.setHorizresolution(72);
        visualSampleEntry.setVertresolution(72);
        visualSampleEntry.setWidth(width);
        visualSampleEntry.setHeight(height);

        AvcConfigurationBox avcConfigurationBox = new AvcConfigurationBox();

        if (format.getByteBuffer("csd-0") != null) {
          ArrayList<byte[]> spsArray = new ArrayList<>();
          ByteBuffer spsBuff = format.getByteBuffer("csd-0");
          spsBuff.position(4);
          byte[] spsBytes = new byte[spsBuff.remaining()];
          spsBuff.get(spsBytes);
          spsArray.add(spsBytes);

          ArrayList<byte[]> ppsArray = new ArrayList<>();
          ByteBuffer ppsBuff = format.getByteBuffer("csd-1");
          ppsBuff.position(4);
          byte[] ppsBytes = new byte[ppsBuff.remaining()];
          ppsBuff.get(ppsBytes);
          ppsArray.add(ppsBytes);
          avcConfigurationBox.setSequenceParameterSets(spsArray);
          avcConfigurationBox.setPictureParameterSets(ppsArray);
        }

        if (format.containsKey("level")) {
          int level = format.getInteger("level");
          if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel1) {
            avcConfigurationBox.setAvcLevelIndication(1);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel2) {
            avcConfigurationBox.setAvcLevelIndication(2);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel11) {
            avcConfigurationBox.setAvcLevelIndication(11);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel12) {
            avcConfigurationBox.setAvcLevelIndication(12);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel13) {
            avcConfigurationBox.setAvcLevelIndication(13);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel21) {
            avcConfigurationBox.setAvcLevelIndication(21);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel22) {
            avcConfigurationBox.setAvcLevelIndication(22);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel3) {
            avcConfigurationBox.setAvcLevelIndication(3);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel31) {
            avcConfigurationBox.setAvcLevelIndication(31);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel32) {
            avcConfigurationBox.setAvcLevelIndication(32);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel4) {
            avcConfigurationBox.setAvcLevelIndication(4);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel41) {
            avcConfigurationBox.setAvcLevelIndication(41);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel42) {
            avcConfigurationBox.setAvcLevelIndication(42);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel5) {
            avcConfigurationBox.setAvcLevelIndication(5);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel51) {
            avcConfigurationBox.setAvcLevelIndication(51);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel52) {
            avcConfigurationBox.setAvcLevelIndication(52);
          } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel1b) {
            avcConfigurationBox.setAvcLevelIndication(0x1b);
          }
        } else {
          avcConfigurationBox.setAvcLevelIndication(13);
        }
        if (format.containsKey("profile")) {
          int profile = format.getInteger("profile");
          if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline) {
            avcConfigurationBox.setAvcProfileIndication(66);
          } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileMain) {
            avcConfigurationBox.setAvcProfileIndication(77);
          } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileExtended) {
            avcConfigurationBox.setAvcProfileIndication(88);
          } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh) {
            avcConfigurationBox.setAvcProfileIndication(100);
          } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh10) {
            avcConfigurationBox.setAvcProfileIndication(110);
          } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh422) {
            avcConfigurationBox.setAvcProfileIndication(122);
          } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh444) {
            avcConfigurationBox.setAvcProfileIndication(244);
          }
        } else {
          avcConfigurationBox.setAvcProfileIndication(100);
        }
        avcConfigurationBox.setBitDepthLumaMinus8(-1);
        avcConfigurationBox.setBitDepthChromaMinus8(-1);
        avcConfigurationBox.setChromaFormat(-1);
        avcConfigurationBox.setConfigurationVersion(1);
        avcConfigurationBox.setLengthSizeMinusOne(3);
        avcConfigurationBox.setProfileCompatibility(0);

        visualSampleEntry.addBox(avcConfigurationBox);
        sampleDescriptionBox.addBox(visualSampleEntry);
      } else if (mime.equals("video/mp4v")) {
        VisualSampleEntry visualSampleEntry = new VisualSampleEntry("mp4v");
        visualSampleEntry.setDataReferenceIndex(1);
        visualSampleEntry.setDepth(24);
        visualSampleEntry.setFrameCount(1);
        visualSampleEntry.setHorizresolution(72);
        visualSampleEntry.setVertresolution(72);
        visualSampleEntry.setWidth(width);
        visualSampleEntry.setHeight(height);

        sampleDescriptionBox.addBox(visualSampleEntry);
      }
    } else {
      volume = 1;
      timeScale = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
      handler = "soun";
      headerBox = new SoundMediaHeaderBox();
      sampleDescriptionBox = new SampleDescriptionBox();
      AudioSampleEntry audioSampleEntry = new AudioSampleEntry("mp4a");
      audioSampleEntry.setChannelCount(format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
      audioSampleEntry.setSampleRate(format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
      audioSampleEntry.setDataReferenceIndex(1);
      audioSampleEntry.setSampleSize(16);

      ESDescriptorBox esds = new ESDescriptorBox();
      ESDescriptor descriptor = new ESDescriptor();
      descriptor.setEsId(0);

      SLConfigDescriptor slConfigDescriptor = new SLConfigDescriptor();
      slConfigDescriptor.setPredefined(2);
      descriptor.setSlConfigDescriptor(slConfigDescriptor);

      String mime;
      if (format.containsKey("mime")) {
        mime = format.getString("mime");
      } else {
        mime = "audio/mp4-latm";
      }

      DecoderConfigDescriptor decoderConfigDescriptor = new DecoderConfigDescriptor();
      if ("audio/mpeg".equals(mime)) {
        decoderConfigDescriptor.setObjectTypeIndication(0x69);
      } else {
        decoderConfigDescriptor.setObjectTypeIndication(0x40);
      }
      decoderConfigDescriptor.setStreamType(5);
      decoderConfigDescriptor.setBufferSizeDB(1536);
      if (format.containsKey("max-bitrate")) {
        decoderConfigDescriptor.setMaxBitRate(format.getInteger("max-bitrate"));
      } else {
        decoderConfigDescriptor.setMaxBitRate(96000);
      }
      decoderConfigDescriptor.setAvgBitRate(timeScale);

      AudioSpecificConfig audioSpecificConfig = new AudioSpecificConfig();
      audioSpecificConfig.setAudioObjectType(2);
      audioSpecificConfig.setSamplingFrequencyIndex(samplingFrequencyIndexMap.get((int) audioSampleEntry.getSampleRate()));
      audioSpecificConfig.setChannelConfiguration(audioSampleEntry.getChannelCount());
      decoderConfigDescriptor.setAudioSpecificInfo(audioSpecificConfig);

      descriptor.setDecoderConfigDescriptor(decoderConfigDescriptor);

      ByteBuffer data = descriptor.serialize();
      esds.setEsDescriptor(descriptor);
      esds.setData(data);
      audioSampleEntry.addBox(esds);
      sampleDescriptionBox.addBox(audioSampleEntry);
    }
  }

  public long getTrackId() {
    return trackId;
  }

  public void addSample(long offset, MediaCodec.BufferInfo bufferInfo) {
    boolean isSyncFrame = !isAudio && (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
    samples.add(new Sample(offset, bufferInfo.size));
    if (syncSamples != null && isSyncFrame) {
      syncSamples.add(samples.size());
    }
    samplePresentationTimes.add(new SamplePresentationTime(samplePresentationTimes.size(), (bufferInfo.presentationTimeUs * timeScale + 500000L) / 1000000L));
  }

  public void prepare() {
    duration = 0;

    ArrayList<SamplePresentationTime> original = new ArrayList<>(samplePresentationTimes);
    Collections.sort(samplePresentationTimes, (o1, o2) -> {
      if (o1.presentationTime > o2.presentationTime) {
        return 1;
      } else if (o1.presentationTime < o2.presentationTime) {
        return -1;
      }
      return 0;
    });
    long lastPresentationTimeUs = 0;
    sampleDurations = new long[samplePresentationTimes.size()];
    long minDelta = Long.MAX_VALUE;
    boolean outOfOrder = false;
    for (int a = 0; a < samplePresentationTimes.size(); a++) {
      SamplePresentationTime presentationTime = samplePresentationTimes.get(a);
      long delta = presentationTime.presentationTime - lastPresentationTimeUs;
      lastPresentationTimeUs = presentationTime.presentationTime;
      sampleDurations[presentationTime.index] = delta;
      if (presentationTime.index != 0) {
        duration += delta;
      }
      if (delta > 0 && delta < Integer.MAX_VALUE) {
        minDelta = Math.min(minDelta, delta);
      }
      if (presentationTime.index != a) {
        outOfOrder = true;
      }
    }
    if (sampleDurations.length > 0) {
      sampleDurations[0] = minDelta;
      duration += minDelta;
    }
    for (int a = 1; a < original.size(); a++) {
      original.get(a).dt = sampleDurations[a] + original.get(a - 1).dt;
    }
    if (outOfOrder) {
      sampleCompositions = new int[samplePresentationTimes.size()];
      for (int a = 0; a < samplePresentationTimes.size(); a++) {
        SamplePresentationTime presentationTime = samplePresentationTimes.get(a);
        sampleCompositions[presentationTime.index] = (int) (presentationTime.presentationTime - presentationTime.dt);
      }
    }
    //if (!first) {
    //    sampleDurations.add(sampleDurations.size() - 1, delta);
    //    duration += delta;
    //}
  }

  public ArrayList<Sample> getSamples() {
    return samples;
  }

  public long getDuration() {
    return duration;
  }

  public String getHandler() {
    return handler;
  }

  public AbstractMediaHeaderBox getMediaHeaderBox() {
    return headerBox;
  }

  public int[] getSampleCompositions() {
    return sampleCompositions;
  }

  public SampleDescriptionBox getSampleDescriptionBox() {
    return sampleDescriptionBox;
  }

  public long[] getSyncSamples() {
    if (syncSamples == null || syncSamples.isEmpty()) {
      return null;
    }
    long[] returns = new long[syncSamples.size()];
    for (int i = 0; i < syncSamples.size(); i++) {
      returns[i] = syncSamples.get(i);
    }
    return returns;
  }

  public int getTimeScale() {
    return timeScale;
  }

  public Date getCreationTime() {
    return creationTime;
  }

  public int getWidth() {
    return width;
  }

  public int getHeight() {
    return height;
  }

  public float getVolume() {
    return volume;
  }

  public long[] getSampleDurations() {
    return sampleDurations;
  }

  public boolean isAudio() {
    return isAudio;
  }
}
